﻿using DimensionsHelper.Common.Contracts.Excel;

namespace DimensionsHelper.Client.WinUi.Controls;

[DebuggerDisplay("{ToString(),nq}")]
public class BorderGridCell
{
    private readonly CompositionSpriteShape?[] _borders;

    private readonly Compositor _compositor;
    private readonly BorderGrid _owner;
    private readonly Vector2[] _points;
    private readonly ShapeVisual _shapeVisual;
    private BorderCellFlags _cellFlags = BorderCellFlags.None;


    public BorderGridCell(BorderGrid grid, Compositor compositor, ShapeVisual visual, int row, int column)
    {
        _owner = grid;
        Row = row;
        Column = column;
        _compositor = compositor;
        _shapeVisual = visual;
        _borders = [null, null, null, null];

        if (Column == grid.Columns - 1)
        {
            AddFlags(BorderCellFlags.RightBound);
        }

        if (Row == grid.Rows - 1)
        {
            AddFlags(BorderCellFlags.BottomBound);
        }

        _points = new Vector2[4];
        InitBorder();
    }


    public int Row
    {
        get;
    }

    public int Column
    {
        get;
    }


    public BorderGridCell? Left
    {
        get;
        internal set;
    }


    public BorderGridCell? Top
    {
        get;
        internal set;
    }


    public BorderGridCell? Right
    {
        get;
        internal set;
    }


    public BorderGridCell? Bottom
    {
        get;
        internal set;
    }


    public BorderGridRange? ContainingRange
    {
        get;
        internal set;
    }

    public bool IsRightDrawingBound => HasFlags(BorderCellFlags.RightBound);

    public bool IsBottomDrawingBound => HasFlags(BorderCellFlags.BottomBound);


    public bool IsHidden
    {
        get => HasFlags(BorderCellFlags.Hidden);
        set
        {
            if (value == HasFlags(BorderCellFlags.Hidden))
            {
                return;
            }

            UpdateDrawingBoundHiding(value);
            bool isMerged = ContainingRange?.IsMerged == true;

            if (value && isMerged && !HasFlags(BorderCellFlags.MergedRangeInnerCell))
            {
                // +-------+-------+
                // |       | HIDE  |
                // +-------+-------+
                //         ^       ^
                //  RightBound <- RightBound
                UpdateNeighbourStateHiding(Left, BorderCellFlags.MergedRangeRightBoundCell, BorderGridRange.X2,
                    Left?.Column);
                // +----------+
                // |          |
                // +----------+ < BottomBound
                // |   HIDE   |        ^
                // +----------+ < BottomBound
                UpdateNeighbourStateHiding(Top, BorderCellFlags.MergedRangeBottomBoundCell, BorderGridRange.Y2,
                    Top?.Row);
                //        +-------+-------+
                //        | HIDE  |       |
                //        +-------+-------+
                //        ^       ^
                // LeftBound <- LeftBound
                UpdateNeighbourStateHiding(Right, BorderCellFlags.MergedRangeLeftBoundCell, BorderGridRange.X1,
                    Right?.Column);
                // +----------+ < TopBound
                // |  UNHIDE  |      V
                // +----------+ < TopBound 
                // |          |
                // +----------+
                UpdateNeighbourStateHiding(Bottom, BorderCellFlags.MergedRangeTopBoundCell, BorderGridRange.Y1,
                    Bottom?.Row);
            }
            else if (isMerged && !HasFlags(BorderCellFlags.MergedRangeInnerCell))
            {
                // +-------+----------+
                // |       |  UNHIDE  |
                // +-------+----------+
                //         ^          ^
                //  RightBound -> RightBound
                UpdateNeighbourStateUnHiding(Left, BorderCellFlags.MergedRangeRightBoundCell, BorderGridRange.X2,
                    Column);
                // +----------+
                // |          |
                // +----------+ < BottomBound
                // |  UNHIDE  |        V
                // +----------+ < BottomBound
                UpdateNeighbourStateUnHiding(Top, BorderCellFlags.MergedRangeBottomBoundCell, BorderGridRange.Y2, Row);
                //     +----------+-------+
                //     |  UNHIDE  |       |
                //     +----------+-------+
                //     ^          ^
                // LeftBound -> LeftBound
                UpdateNeighbourStateUnHiding(Right, BorderCellFlags.MergedRangeLeftBoundCell, BorderGridRange.X1,
                    Column);
                // +----------+ < TopBound
                // |  UNHIDE  |      ^
                // +----------+ < TopBound 
                // |          |
                // +----------+
                UpdateNeighbourStateUnHiding(Bottom, BorderCellFlags.MergedRangeTopBoundCell, BorderGridRange.Y1, Row);
            }

            UpdateBorder(value);
        }
    }


    public override string ToString() => $"<{Row}, {Column}> - {_cellFlags}";

    private CompositionSpriteShape CreateSpriteShape()
    {
        CompositionLineGeometry line = _compositor.CreateLineGeometry();
        CompositionSpriteShape shape = _compositor.CreateSpriteShape(line);
        _shapeVisual.Shapes.Add(shape);
        shape.Offset = BorderGridRangeCollection.CellOffset;
        return shape;
    }


    private void InitBorder()
    {
        EnsureBorderShape(BorderIndex.Left);
        EnsureBorderShape(BorderIndex.Top);

        if (IsRightDrawingBound)
        {
            EnsureBorderShape(BorderIndex.Right);
        }

        if (IsBottomDrawingBound)
        {
            EnsureBorderShape(BorderIndex.Bottom);
        }
    }


    internal void UpdateVertexCoordinates(double rowOffset, double columnOffset)
    {
        double rowHeight = _owner.RowDefinitions[Row].ActualHeight;
        double colWidth = _owner.ColumnDefinitions[Column].ActualWidth;
        _points[0] = new Vector2((float)columnOffset, (float)rowOffset);
        _points[1] = new Vector2((float)(columnOffset + colWidth), (float)rowOffset);
        _points[2] = new Vector2((float)(columnOffset + colWidth), (float)(rowOffset + rowHeight));
        _points[3] = new Vector2((float)columnOffset, (float)(rowOffset + rowHeight));

        SetLineVectors();
    }


    private void SetLineVectors()
    {
        for (int i = 0; i < 4; i++)
        {
            CompositionSpriteShape? lineShape = _borders[i];

            if (lineShape == null)
            {
                continue;
            }

            CompositionLineGeometry line = (CompositionLineGeometry)lineShape.Geometry;
            line.Start = _points[i];
            line.End = _points[i == 0 ? 3 : i - 1];
        }
    }


    private void EnsureBorderShape(BorderIndex index) => _borders[(int)index] ??= CreateSpriteShape();


    internal void AddFlags(BorderCellFlags flags) => _cellFlags |= flags;


    internal void ClearFlags(BorderCellFlags flags) => _cellFlags &= ~flags;


    internal void AddOrClearFlags(bool condition, BorderCellFlags flags)
    {
        if (condition)
        {
            AddFlags(flags);
        }
        else
        {
            ClearFlags(flags);
        }
    }


    internal bool HasFlags(BorderCellFlags flags) => (_cellFlags & flags) != 0;


    public BorderGridCell? GetNextRightVisibleCell()
    {
        BorderGridCell? cell = Right;

        while (cell != null)
        {
            if (!cell.IsHidden)
            {
                break;
            }

            cell = cell.Right;
        }

        return cell;
    }


    public BorderGridCell? GetNextBottomVisibleCell()
    {
        BorderGridCell? cell = Bottom;

        while (cell != null)
        {
            if (!cell.IsHidden)
            {
                break;
            }

            cell = cell.Bottom;
        }

        return cell;
    }


    private static BorderInfo? GetBorderInfoOfPosition(BorderGridCell? cell, BorderPosition position,
        int levelUntil = -1)
    {
        if (cell == null)
        {
            return null;
        }

        BorderInfo? info = null;
        BorderGridRange? range = cell.ContainingRange;

        while (range != null)
        {
            // 跳过不设置边框的区间
            if (!range.IsBorderVisible)
            {
                range = range.ContainingRange;
                continue;
            }

            if (levelUntil >= range.Level)
            {
                return null;
            }

            bool isLeftBound = position == BorderPosition.Left && range.Column == cell.Column;
            bool isTopBound = position == BorderPosition.Top && range.Row == cell.Row;

            // 如果 position == BorderPosition.Left || BorderPosition.Top，考虑是否是区间的外边框
            // 如果是左边界，不添加边框
            if (isTopBound || isLeftBound)
            {
                if (range.AddAroundBorder)
                {
                    return new BorderInfo(range.BorderColor, range.BorderStyle);
                }

                range = range.ContainingRange;
                continue;
            }

            if (info == null && range.BorderPosition.HasFlag(position) && range.BorderStyle != BorderStyle.None)
            {
                // 优先级低于外边框
                info = new BorderInfo(range.BorderColor, range.BorderStyle);
            }

            range = range.ContainingRange;
        }

        return info;
    }


    private int GetRelativeRangeLevel(BorderGridRange? relativeRange)
    {
        if (relativeRange == null || ContainingRange == null)
        {
            return -1;
        }

        BorderGridRange? range = relativeRange;
        while (range != null)
        {
            if (range == ContainingRange)
            {
                return range.Level;
            }

            range = range.ContainingRange;
        }

        return -1;
    }


    private BorderInfo GetBorderInfo(BorderPosition position)
    {
        BorderInfo? result = GetBorderInfoOfPosition(this, position);

        // 其次查找临近边框
        if (result == null && position.HasFlag(BorderPosition.Left) &&
            TryFindRangeRightNeighbour(out BorderGridCell? leftVisible))
        {
            result = GetBorderInfoOfPosition(leftVisible, BorderPosition.Around,
                GetRelativeRangeLevel(leftVisible.ContainingRange));
        }
        else if (result == null && position.HasFlag(BorderPosition.Top) &&
                 TryFindRangeBottomNeighbour(out BorderGridCell? topVisible))
        {
            result = GetBorderInfoOfPosition(topVisible, BorderPosition.Around,
                GetRelativeRangeLevel(topVisible?.ContainingRange));
        }

        return result ?? new BorderInfo(_owner.BorderColor, BorderStyle.Thin);
    }


    public BorderGridRange? GetContainingBorderVisibleRange()
    {
        BorderGridRange? range = ContainingRange;

        while (range != null)
        {
            if (range.IsBorderVisible)
            {
                return range;
            }

            range = range.ContainingRange;
        }

        return null;
    }


    public static bool TryFindRangeNeighbourCell(BorderGridCell cell, Func<BorderGridCell, BorderGridCell?> getter,
        [MaybeNullWhen(false)] out BorderGridCell visibleCell)
    {
        visibleCell = null;
        BorderGridCell? neighbourCell = getter(cell);
        BorderGridRange? containingRange = cell.GetContainingBorderVisibleRange();

        while (neighbourCell != null)
        {
            if (!neighbourCell.IsHidden)
            {
                BorderGridRange? visibleRange = neighbourCell.GetContainingBorderVisibleRange();
                visibleCell = neighbourCell;
                return containingRange != visibleRange && visibleRange?.IsSubRangeOrThis(containingRange) != true;
            }

            neighbourCell = getter(neighbourCell);
        }

        return false;
    }


    public bool TryFindRangeRightNeighbour([MaybeNullWhen(false)] out BorderGridCell leftVisibleCell) =>
        TryFindRangeNeighbourCell(this, c => c.Left, out leftVisibleCell);

    public bool TryFindRangeBottomNeighbour([MaybeNullWhen(false)] out BorderGridCell? topVisibleCell) =>
        TryFindRangeNeighbourCell(this, c => c.Top, out topVisibleCell);


    private void UpdateDrawingBoundHiding(bool isHidden)
    {
        AddOrClearFlags(isHidden, BorderCellFlags.Hidden);

        if (IsBottomDrawingBound)
        {
            Top?.AddOrClearFlags(isHidden, BorderCellFlags.BottomBound);
            if (isHidden)
            {
                Top?.EnsureBorderShape(BorderIndex.Bottom);
            }
            else
            {
                Top?.ClearBorderShape(BorderIndex.Bottom);
            }
        }

        if (IsRightDrawingBound)
        {
            Left?.AddOrClearFlags(isHidden, BorderCellFlags.RightBound);
            if (isHidden)
            {
                Left?.EnsureBorderShape(BorderIndex.Right);
            }
            else
            {
                Left?.ClearBorderShape(BorderIndex.Right);
            }
        }
    }


    private void UpdateNeighbourStateHiding(BorderGridCell? neighbour, BorderCellFlags neighbourFlags, int boundIndex,
        int? targetBound)
    {
        if (ContainingRange?.IsDirectlyContained(neighbour) != true || !ContainingRange.IsBoundCell(this, boundIndex))
        {
            return;
        }

        neighbour?.AddFlags(neighbourFlags);
        neighbour?.ClearFlags(BorderCellFlags.MergedRangeInnerCell);
        // 将当前单元格的标记移动到临近单元格上
        ClearFlags(neighbourFlags);
        ContainingRange?.UpdateVisibleBound(boundIndex, targetBound);
    }


    private void UpdateNeighbourStateUnHiding(BorderGridCell? neighbour, BorderCellFlags neighbourFlags, int boundIndex,
        int targetBound)
    {
        if (ContainingRange?.IsDirectlyContained(neighbour) != true ||
            !ContainingRange.IsBoundCell(neighbour, boundIndex))
        {
            return;
        }

        ContainingRange.UpdateVisibleBound(boundIndex, targetBound);
        // 将临近单元格的标记移动到当前单元格上
        AddFlags(neighbourFlags);
        neighbour?.ClearFlags(neighbourFlags);

        // 更新边界后重新检查是否是内部单元格
        if (ContainingRange.IsInnerCell(neighbour))
        {
            neighbour.AddFlags(BorderCellFlags.MergedRangeInnerCell);
        }
    }


    // 单个单元格在渲染时，默认添加左和上两个防线的边框。
    //
    // +-----------------+
    // |                 |  <- 由右侧单元格添加，如果当前单元格是区域的
    // |                 |     最后侧单元格或者Grid的右边界，也要添加。
    // +-----------------+
    //         ^
    //         |
    //  由下侧单元格添加，如果当前单元格是区域的最下方
    //  单元格或者是Grid的下边界，也要添加。


    private static void UpdateDashArray(CompositionSpriteShape shape, BorderStyle style)
    {
        shape.StrokeDashArray.Clear();

        float[] dashArray = BorderHelper.GetDashArray(style);

        foreach (float dash in dashArray)
        {
            shape.StrokeDashArray.Add(dash);
        }
    }


    private void UpdateBorderThickness(CompositionSpriteShape? shape, BorderStyle style)
    {
        if (shape == null)
        {
            return;
        }

        shape.StrokeThickness = (float)BorderHelper.GetThickness(style,
            _owner.ThinBorderThickness,
            _owner.NormalBorderThickness,
            _owner.MediumBorderThickness,
            _owner.ThickBorderThickness);
    }


    private void UpdateBorder(BorderPosition position, BorderIndex index, bool hideLine = false)
    {
        CompositionSpriteShape? border = _borders[(int)index];

        if (border == null)
        {
            return;
        }

        if (hideLine)
        {
            UpdateBorderThickness(border, BorderStyle.None);
            return;
        }

        BorderInfo info = GetBorderInfo(position);
        UpdateDashArray(border, info.Style);
        UpdateBorderThickness(border, info.Style);
        border.StrokeBrush = _compositor.CreateColorBrush(info.Color);
    }


    private void ClearBorderShape(BorderIndex index)
    {
        if (_borders[(int)index] is { } border)
        {
            _shapeVisual.Shapes.Remove(border);
            _borders[(int)index] = null;
        }
    }


    private void ClearBorder()
    {
        for (int i = 0; i < _borders.Length; i++)
        {
            UpdateBorderThickness(_borders[i], BorderStyle.None);
        }
    }

    public void UpdateBorder(bool updateHide)
    {
        if (updateHide)
        {
            if (IsHidden)
            {
                ClearBorder();
            }
            else
            {
                UpdateBorderThickness(_borders[0], GetBorderInfo(BorderPosition.Left).Style);
                UpdateBorderThickness(_borders[1], GetBorderInfo(BorderPosition.Top).Style);
                UpdateBorderThickness(_borders[2], GetBorderInfo(BorderPosition.Right).Style);
                UpdateBorderThickness(_borders[3], GetBorderInfo(BorderPosition.Bottom).Style);
            }

            return;
        }

        if (IsHidden || HasFlags(BorderCellFlags.MergedRangeInnerCell))
        {
            return;
        }

        if (HasFlags(BorderCellFlags.MergedBound))
        {
            UpdateBorder(
                BorderPosition.Left,
                BorderIndex.Left,
                !HasFlags(BorderCellFlags.MergedRangeLeftBoundCell));
            UpdateBorder(
                BorderPosition.Top,
                BorderIndex.Top,
                !HasFlags(BorderCellFlags.MergedRangeTopBoundCell));
        }
        else
        {
            UpdateBorder(BorderPosition.Left, BorderIndex.Left);
            UpdateBorder(BorderPosition.Top, BorderIndex.Top);
        }

        // 右下边界始终要更新

        if (IsRightDrawingBound)
        {
            UpdateBorder(BorderPosition.Around, BorderIndex.Right);
        }

        if (IsBottomDrawingBound)
        {
            UpdateBorder(BorderPosition.Around, BorderIndex.Bottom);
        }
    }


    private enum BorderIndex
    {
        Left = 0,
        Top = 1,
        Right = 2,
        Bottom = 3
    }


    private record BorderInfo(Color Color, BorderStyle Style)
    {
        public static BorderInfo Empty => new(Colors.Transparent, BorderStyle.None);
    }
}

[Flags]
internal enum BorderCellFlags
{
    None = 0,
    RightBound = 0x1,
    BottomBound = 0x2,
    Hidden = 0x4,
    MergedRangeInnerCell = 0x8,
    MergedRangeLeftBoundCell = 0x10,
    MergedRangeTopBoundCell = 0x20,
    MergedRangeRightBoundCell = 0x40,
    MergedRangeBottomBoundCell = 0x80,

    MergedBound = MergedRangeLeftBoundCell | MergedRangeTopBoundCell | MergedRangeRightBoundCell |
                  MergedRangeBottomBoundCell
}